package be.rivendale.mathematics;

import be.rivendale.mathematics.intersection.Intersection;
import be.rivendale.mathematics.intersection.RayIntersectionMode;
import org.apache.commons.lang.Validate;

/**
 * Represents a 2D rectangle in 3D space.
 * This means that the rectangle is guaranteed to be completely in one plane, but that the plane's orientation
 * in the 3D space can be arbitrary.
 */
public class Rectangle {
    private Plane plane;
    /**
     * Constructs a rectangle in 3d space out of three points.
     * The fourth point that completes the rectangle is calculated by translating the vectors between the points.
     * <p>The three points passed as parameters here must define a perpendicular angle with 'a' being the
     * enclosed angle point. (so the corner defined as 'bc' must be orthogonal). This angle point defines a relative position of the rectangle (one of the four corners).
     * The two side points 'b' and 'c' define the two corners that lie next to the 'a' point, and thus also define the
     * height and the width of the rectangle. The fourth point 'd' is calculated out of the other three.<br/>
     * This is a graphical representation of the rectangle:<pre>
     * b ---- d
     * ^      |
     * |      |
     * a ---> c</pre></p>
     * @param a The enclosed point (also called
     * <a href="http://en.wikipedia.org/wiki/Vertex_(geometry)#Of_an_angle">vertex</a>) of the angle. Defines one of the
     * four corners of the rectangle. Point a is also used as the 'base point' or 'origin' when defining points on the
     * rectangle by using the {@link #pointOnRectangle(double, double) parametric equation} of the rectangle.
     * @param b One of the side points of the angle defined by 'abc'. Defines one of the four corners of the rectangle,
     * and also the height of the rectangle (implicitly as the length of the line segment 'ab').
     * @param c One of the side points of the angle defined by 'abc'. Defines one of the four corners of the rectangle,
     * and also the width of the rectangle (implicitly as the length of the line segment 'ac').
     *
     */
    public Rectangle(Point a, Point b, Point c) {
        this.plane = new Plane(a, b, c);
        validatePointDefinePerpendicularCorner(a, b, c);
    }

    /**
     * The three points that are required to define a rectangle, need to be perpendicular to eachother.
     * When the three points are perpendicular, the fourth is also perpendicular automatically.
     * This methods performs a validation check to see if this input contract requirement is met.
     * @param a The vertex of the angle defined by points bc. This is the point enclosed by the other two.
     * @param b One of the outer points in the angle defined by bc.
     * @param c The second of the outer points in the angle defined by bc
     */
    private void validatePointDefinePerpendicularCorner(Point a, Point b, Point c) {
        Vector vectorAToB = Triple.vectorBetweenPoints(a, b);
        Vector vectorAToC = Triple.vectorBetweenPoints(a, c);
        Validate.isTrue(vectorAToB.isPerpendicularTo(vectorAToC), "The corner defined by b?c is not perpendicular");
    }

    /**
     * Returns point a. This point is also the reference point when calculating points on the rectangle with
     * the {@link #pointOnRectangle(double, double) parametric equation} of the plane on which the rectangle lies.
     * @return point a.
     */
    public Point getA() {
        return plane.getA();
    }

    /**
     * Returns point b.
     * @return point b.
     */
    public Point getB() {
        return plane.getB();
    }

    /**
     * Returns point c.
     * @return point c.
     */
    public Point getC() {
        return plane.getC();
    }

    /**
     * Return point d.
     * @return point d.
     */
    public Point getD() {
        return plane.pointOnPlane(1, 1);
    }

    /**
     * Calculates a point on the rectangle.
     * <p>This point is calculated by specifying two parameters: u and v.
     * These are fed into the parametric equation of the plane where the rectangle lies on.
     * The result is that the two parameters are to be interpreted as the relative coordinates of the rectangle
     * when you take the sides 'ab' as the axis for the u coordinate, and another side 'ac' as the axis for the v
     * coordinate. The range of the coordinates is from 0 to 1.</p>
     * <p>This is actually the same as the {@link Plane#pointOnPlane(double, double)} method, but restricted so that
     * the u and v coordinates are within 0 and 1. The coordinates are also guaranteed to be perpendicular, which is
     * optional (but valid) in the plane equation</p>
     * @param u The relative coordinate on the axis defined by the edge 'ab' ranging from 0 to 1.
     * @param v The relative coordinate on the axis defined by the edge 'ac' ranging from 0 to 1.
     * @return A point on the plane, as it's projected into it's own coordinate system with the axes being 'ab' and 'ac'.
     * @see Plane#pointOnPlane(double, double)
     */
    public Point pointOnRectangle(double u, double v) {
        Validate.isTrue(MathematicalUtilities.between(0, 1, u) &&
                MathematicalUtilities.between(0, 1, v), "A rectangle is bounded by it's corner points. The u and v values must range between 0 and 1");
        return plane.pointOnPlane(u, v);
    }

	public Intersection intersection(Ray ray, RayIntersectionMode rayIntersectionMode) {
		return new Intersection(ray, this, rayIntersectionMode);
	}

	/**
	 * Returns the center point of this rectangle.
	 * The center point of the rectangle can be defined in a couple of ways:
	 * <ul><li>The intersection point of it's diagonals</li>
	 * <li>The point when using parametric values of <code>0.5</code> in it's {@link #pointOnRectangle(double, double)
	 * equation}</li><li>...</li></ul>
	 * @return The center point of this rectangle.
	 */
	public Point center() {
		return pointOnRectangle(0.5, 0.5);
	}
}
